package red.stu.pconstraint.engine;

import org.jetbrains.annotations.NotNull;
import red.stu.pconstraint.ProcessPojo;
import red.stu.pconstraint.ProcessStorageApi;
import red.stu.pconstraint.annotation.*;
import red.stu.pconstraint.config.Configuration;
import red.stu.pconstraint.config.XMLConfigBuilder;
import red.stu.pconstraint.config.XMLMapperBuilder;
import red.stu.pconstraint.dto.mapper.Branch;
import red.stu.pconstraint.dto.mapper.ProcessMapper;
import red.stu.pconstraint.engine.exception.NoSuchProcessException;
import red.stu.pconstraint.engine.executor.ProcessNodeExecutor;
import red.stu.pconstraint.io.Resources;
import red.stu.pconstraint.pojo.ExecutionLocation;
import red.stu.pconstraint.scanner.ClassScanner;
import red.stu.pconstraint.scanner.XmlScanner;
import red.stu.pconstraint.util.MethodUtils;
import red.stu.pconstraint.util.PackageStringUtils;
import red.stu.pconstraint.util.StringUtils;
import red.stu.pconstraint.util.annotation.AnnotationUtils;

import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 工作流引擎工厂
 * @author yihrmc @stu.red
 * @date 2019/11/15 19:41
 */
public class WorkFlowEngineFactory {

    /**
     * 默认配置文件资源名
     */
    private static final String DEFAULT_CONFIGURATION_RESOURCE = "pconstraint-config.xml";

    /**
     * 流程引擎配置
     */
    private final Configuration configuration;

    /**
     * 引擎工作空间
     * Map<流程命名空间(流程ID), 工作空间实例>
     */
    //private final Map<String, WorkingSpace> workingSpaceMap;

    /**
     * 流程节点执行者
     * Map<执行者所处方法, 执行者实例>
     */
    private final Map<String, ProcessNodeExecutor> processNodeExecutorMap;

    /**
     * 流程引擎
     * Map<流程命名空间(流程ID), 流程引擎>
     */
    private final Map<String, WorkFlowEngine> workFlowEngineMap;

    /**
     * 流程数据储存工厂
     */
    private AbstractProcessStorageFactory processStorageFactory;

    /**
     * 流程事件监听
     */
    private BaseProcessEventListener processEventListener;

    /**
     * 已注册的流程模版
     *  Map[流程命名空间, 流程Mapper]
     *
     * {@link Map#keySet()} String 流程命名空间，一般与class对应
     * {@link Map#values()} ProcessMapper 流程Mapper
     */
    // private final Map<String, WorkingSpace> processMapperMap;

    /**
     * 执行者
     * Map[流程ID, Map[ExecutorUUID, BaseExecutor]]
     */
    // private final Map<String, Map<String, BaseExecutor>> executorMap;

    /**
     * 工作流引擎 <br/>
     *      - 使用文件名为{@link #DEFAULT_CONFIGURATION_RESOURCE}的配置文件加载
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public WorkFlowEngineFactory(@NotNull AbstractProcessStorageFactory processStorageFactory, @NotNull BaseProcessEventListener processEventListener) throws IOException, ClassNotFoundException {
        this(processStorageFactory, processEventListener, DEFAULT_CONFIGURATION_RESOURCE);
    }

    /**
     * 工作流引擎
     * @param configurationResource 配置文件资源路径
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public WorkFlowEngineFactory(@NotNull AbstractProcessStorageFactory processStorageFactory, @NotNull BaseProcessEventListener processEventListener, @NotNull String configurationResource) throws IOException, ClassNotFoundException {
        this(processStorageFactory, processEventListener, new XMLConfigBuilder(Resources.getResourceAsStream(configurationResource), false).parse());
    }

    /**
     * 工作流引擎
     * @param configuration 配置类
     * @throws ClassNotFoundException
     * @throws IOException
     */
    public WorkFlowEngineFactory(@NotNull AbstractProcessStorageFactory processStorageFactory, @NotNull BaseProcessEventListener processEventListener, @NotNull Configuration configuration) throws ClassNotFoundException, IOException {
        this.processStorageFactory = processStorageFactory;
        this.processEventListener = processEventListener;
        this.configuration = configuration;
        // this.processMapperMap = new HashMap<>(16);
        // this.workingSpaceMap = new HashMap<>(16);
        // this.executorMap = new HashMap<>(16);
        this.processNodeExecutorMap = new HashMap<>(32);
        this.workFlowEngineMap = new HashMap<>(16);
        mapperScanner();
        executorScanner();
    }

    /**
     * 扫描所有配置文件
     * @throws IOException
     * @throws ClassNotFoundException
     */
    protected void mapperScanner() throws IOException {
        XmlScanner xmlScanner = new XmlScanner();

        List<Configuration.Mapper> mapperList = configuration.getMapperList();
        if (mapperList != null && mapperList.size() > 0) {
            for (Configuration.Mapper mapper : mapperList) {
                Configuration.Mapper.Type type = Configuration.Mapper.Type.typeOf(mapper.getType());
                String value = mapper.getValue();
                switch (type) {
                    case Resources: {
                        List<String> resourceList = xmlScanner.scan(value);
                        for (String resource : resourceList) {
                            registerMapper(resource);
                        }
                        break;
                    }
                    case Resource: {
                        File xmlFile = Resources.getResourceAsFile(value);
                        registerMapper(xmlFile);
                        break;
                    }
                    case Url: {
                        registerMapper(new File(new URL(value).getPath()));
                        break;
                    }
                    case ClassName: {
                        String resource = PackageStringUtils.packageToPath(value);
                        registerMapper(resource);
                        break;
                    }
                    default: {
                        throw new RuntimeException("Configuration.Mapper.Type not found: " + mapper.getType());
                    }
                }
            }
        }
    }

    /**
     * 扫描所有执行者
     * @throws ClassNotFoundException
     */
    protected void executorScanner() throws ClassNotFoundException, IOException {
        ClassScanner classScanner = new ClassScanner();
        List<Configuration.Scanner> scannerList = configuration.getScannerList();
        if (scannerList != null && scannerList.size() > 0) {
            for (Configuration.Scanner scanner : scannerList) {
                if (Configuration.SCANNER_TYPE_PACKAGE.equalsIgnoreCase(scanner.getType())) {
                    String packageName = scanner.getValue();
                    List<String> classNames = classScanner.scan(packageName);
                    if (classNames != null && classNames.size() > 0) {
                        for (String className : classNames) {
                            registerExecutor(className);
                        }
                    }

                } else if (Configuration.SCANNER_TYPE_CLASS.equalsIgnoreCase(scanner.getType())) {
                    String className = scanner.getValue();
                    registerExecutor(className);
                }
            }
        }
    }

    /**
     * 注册流程执行者
     * @param className 执行者类名
     * @throws ClassNotFoundException
     */
    public void registerExecutor(String className) throws ClassNotFoundException, IOException {
        Class<?> aClass = Class.forName(className);
        registerExecutor(aClass);
    }

    /**
     * 注册流程执行者
     * @param aClass 执行者类
     */
    public void registerExecutor(Class<?> aClass) throws IOException {
        // TODO E 注册流程执行者

        // ProcessConstraint注解加载Mapper文件
        ProcessConstraint processConstraint = AnnotationUtils.getAnnotation(aClass, ProcessConstraint.class);
        if (processConstraint != null) {
            FileResource fileResourceAnnotation = processConstraint.resource();
            if (fileResourceAnnotation == null) {
                fileResourceAnnotation = AnnotationUtils.getAnnotation(aClass, FileResource.class);
            }
            if (fileResourceAnnotation != null) {
                String resource = fileResourceAnnotation.resource();
                String url = fileResourceAnnotation.url();
                if (!StringUtils.isEmpty(resource)) {
                    registerMapper(resource);

                } else if (!StringUtils.isEmpty(url)) {
                    registerMapper(new File(new URL(url).getPath()));

                }
            }
        }

        // 加载流程分支
        ProcessBranch classProcessBranch = AnnotationUtils.getAnnotation(aClass, ProcessBranch.class);

        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            ProcessNode processNode = AnnotationUtils.getAnnotation(method, ProcessNode.class);
            ProcessTimerNode processTimerNode = AnnotationUtils.getAnnotation(method, ProcessTimerNode.class);
            if (processNode != null && processTimerNode != null) {
                throw new RuntimeException("@Processnode and @ processtimernode cannot be used at the same time");
            }
            if (processNode == null && processTimerNode == null) {
                continue;
            }

            ProcessBranch methodProcessBranch = AnnotationUtils.getAnnotation(method, ProcessBranch.class);
            if (methodProcessBranch == null) {
                methodProcessBranch = classProcessBranch;
            }
            if (methodProcessBranch == null) {
                throw new RuntimeException("Branch not specified through @ processbranch");
            }
            String processId = methodProcessBranch.processId();
            if (StringUtils.isEmpty(processId)) {
                Class<?> processIdClass = methodProcessBranch.processIdClass();
                if (!Void.class.equals(processIdClass)) {
                    processId = processIdClass.getName();
                } else {
                    // now class
                    processId = aClass.getName();
                }
            }
            // 获取流程ID对应的流程模版
            WorkFlowEngine workFlowEngine = workFlowEngineMap.get(processId);
            if (workFlowEngine == null) {
                throw new RuntimeException("ProcessMapper not found: " + processId);
            }
            WorkingSpace workingSpace = workFlowEngine.getSpace();
            ProcessMapper processMapper = workingSpace.getProcessMapper();

            String branchId = methodProcessBranch.branchId();
            Branch branch = processMapper.getBranch(branchId);
            if (branch == null) {
                throw new RuntimeException("ProcessMapper Node not found: processId[" +processId
                        + "] branchId[" + branchId + "]");
            }

            if (processNode != null) {
                String nodeId = processNode.nodeId();
                if (StringUtils.isEmpty(nodeId)) {
                    nodeId = method.getName();
                }
                boolean containsBranchNode = branch.containsBranchNode(nodeId);
                if (!containsBranchNode) {
                    throw new RuntimeException("ProcessMapper Node not found: processId[" +processId
                            + "] branchId[" + branchId + "] nodeId[" + nodeId + "]");
                }

                ProcessNodeExecutor baseExecutor = (ProcessNodeExecutor) new ProcessNodeExecutor()
                        .setNodeId(nodeId)
                        .setBranchId(branchId)
                        .setProcessId(processId);
                String methodString = MethodUtils.toString(method);
                processNodeExecutorMap.put(methodString, baseExecutor);
            }

            if (processTimerNode != null) {
                String timerNodeId = processTimerNode.timerNodeId();
                if (StringUtils.isEmpty(timerNodeId)) {
                    timerNodeId = method.getName();
                }

                // FIXME E 储存搜索的定时节点
                // timerNode 不在Mapper中定义，不用查找对应节点 //

//                BaseExecutor executor = new ProcessTimerNodeExecutor(
//                        new ProcessTimerNodeUUID()
//                                .setBranchId(branchId)
//                                .setTimerNodeId(timerNodeId)
//
//                ).setAClass(aClass).setMethod(method);
//                registerExecutor(processId, executor);
            }

        }
    }

    /**
     * 注册流程模版
     * @param resource mapper的资源路径
     * @throws IOException
     */
    public void registerMapper(String resource) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream(resource);
        registerMapper(inputStream);
    }

    /**
     * 注册流程模版
     * @param xmlFile mapper的文件路径
     * @throws FileNotFoundException
     */
    public void registerMapper(File xmlFile) throws FileNotFoundException {
        FileInputStream inputStream = new FileInputStream(xmlFile);
        registerMapper(inputStream);
    }

    /**
     * 注册流程模版
     * @param inputStream mapper内容
     */
    public void registerMapper(InputStream inputStream) {
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration, false);
        ProcessMapper processMapper = xmlMapperBuilder.parse();
        String processId = processMapper.getNamespace();
        if (workFlowEngineMap.containsKey(processId)) {
            throw new RuntimeException("Duplicate namespace(processId) for mapper: " + processId);
        }
        ProcessStorageApi processStorageApi = processStorageFactory.getProcessStorage(processMapper);
        WorkingSpace workingSpace = new WorkingSpace(processMapper, processStorageApi, processId);
        WorkFlowEngine workFlowEngine = new WorkFlowEngine(workingSpace, processEventListener);
        workFlowEngineMap.put(processId, workFlowEngine);
    }

    /**
     * 获取流程节点方法
     * @param aClass
     * @param methodName
     * @return
     */
    private static Method getProcessMethod(Class<?> aClass, String methodName) {
        Method[] declaredMethods = aClass.getDeclaredMethods();
        Method res = null;
        for (Method declaredMethod : declaredMethods) {
            if (methodName.equals(declaredMethod.getName())) {
                Class<?>[] types = declaredMethod.getParameterTypes();
                for (Class<?> type : types) {
                    if (ProcessPojo.class.isAssignableFrom(type)) {
                        if (res != null) {
                            throw new RuntimeException("匹配到过多的方法！");
                        }
                        res = declaredMethod;
                        break;
                    }
                }
            }
        }
        return res;
    }

    /**
     * 启动流程实例解析 <br/>
     *      - 流程方法必须有{@link ProcessNode}且注入成功 <br/>
     *      - 流程方法同名的重载方法不得有实现{@link ProcessPojo}接口的参数 <br/>
     *      - 流程方法必须有实现{@link ProcessPojo}接口的参数 <br/>
     *
     * @param noteStackTrace 流程方法StackTraceElement
     * @param processPojo 流程数据
     * @return
     */
    public WorkFlowEngine.EngineUnit startProcessInstance(StackTraceElement noteStackTrace, ProcessPojo processPojo) {
        String className = noteStackTrace.getClassName();
        String methodName = noteStackTrace.getMethodName();
        Class<?> aClass;
        try {
            aClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        Method method = getProcessMethod(aClass, methodName);
        if (method == null) {
            throw new RuntimeException("匹配不到方法");
        }

        ProcessNodeExecutor executor = processNodeExecutorMap.get(MethodUtils.toString(method));
        if (executor == null) {
            throw new NoSuchProcessException("method no such: " + MethodUtils.toString(method));
        }
        String processId = executor.getProcessId();
        String processInstanceId = processPojo.getProcessInstanceId();

        WorkFlowEngine workFlowEngine = workFlowEngineMap.get(processId);
        ExecutionLocation locationDO = new ExecutionLocation()
                .setBranchId(executor.getBranchId())
                .setNodeId(executor.getNodeId());

        WorkFlowEngine.EngineUnit engineUnit = workFlowEngine.getEngineUnit(processInstanceId, processId, locationDO);
        return engineUnit;
    }

}
